/**
 * Point
 * @author wds
 * @date 2022.12.26
 */

class Point {
    constructor(x, y, a) {
        this.x = x;
        this.y = y;
        this.a = a;
    }

    round() {
        this.x = Math.round(this.x);
        this.y = Math.round(this.y);
        return this;
    }

    equals(point) {
        if (this.x !== point.x) return false;
        return this.y === point.y;
    }

    distance(point) {
        let dx = this.x - point.x,
            dy = this.y - point.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    middle(point) {
        let x = (this.x + point.x) / 2,
            y = (this.y + point.y) / 2;
        return new Point(x, y);
    }

    angle(point) {
        let angle = Point.getDegrees(Math.atan2(this.y - point.y, this.x - point.x));
        if (angle < 0) angle += 360;
        if (angle > 360) angle -= 360;
        return angle;
    }

    rotate(point, angle) {
        angle = Point.getRadians(angle);
        let x1 = point.x + (this.x * Math.cos(angle) - this.y * Math.sin(angle)),
            y1 = point.y + (this.y * Math.cos(angle) + this.x * Math.sin(angle));
        return new Point(x1, y1);
    }

    isContained(points) {
        for (let i = 0; i < points.length; i++) {
            if (points[i].equals(this)) return true;
        }
        return false;
    }

    /**
     * @param p Point
     * @return 0: up, 1: right, 2: down, 3: left
     */
    direction(p) {
        let ang = this.angle(p),
            rtn = 0;
        if (ang > 45 && ang <= 135) rtn = 0;
        else if (ang > 135 && ang <= 225) rtn = 1;
        else if (ang > 225 && ang <= 315) rtn = 2;
        else if (ang > 315 || ang <= 45) rtn = 3;
        return rtn;
    }

    static equals(p1, p2) {
        if (p1.x !== p2.x) return false;
        return p1.y === p2.y;
    }

    static getDegrees(value) {
        value *= 180 / Math.PI;
        if (value < 0) value += 360;
        if (value > 360) value -= 360;
        return value;
    }

    static getRadians(value) {
        return value * (Math.PI / 180);
    }

    static getAngleDelta(a1, a2) {
        let delta = Math.abs(a1 - a2);
        if (delta > 180) delta = Math.abs(delta - 360);
        return delta;
    }

    static getOriginPointByPanEvents(target, e, ratio = 1) {
        let rect = target.getBoundingClientRect();
        let x = e.center.x - e.deltaX,
            y = e.center.y - e.deltaY;
        return new Point((x - rect.left) * ratio, (y - rect.top) * ratio);
    }

    static getInstanceByMouseOffset(e, target, ratio = 1) {
        // console.log('[Point] getInstanceByMouseOffset: ', e, target);
        // returns cross browser cursor position relative to element
        if (!target) target = e.target || e.srcElement;
        let rect = target.getBoundingClientRect();

        // let x = e.type.indexOf('touch') < 0 ? e.clientX : e.touches[0].clientX,
        //     y = e.type.indexOf('touch') < 0 ? e.clientY : e.touches[0].clientY;
        let p = e;
        if (e.type.indexOf('touch') < 0 && typeof e.clientX !== 'undefined') p = e;
        else if (e.touches && e.touches.length) p = e.touches[0];
        else if (e.targetTouches && e.targetTouches.length) p = e.targetTouches[0];
        else if (e.changedTouches && e.changedTouches.length) p = e.changedTouches[0];
        let x = p.clientX,
            y = p.clientY;
        return new Point((x - rect.left) * ratio, (y - rect.top) * ratio);
    }

    static getInstanceByPointerPagePos(e) {
        let pageX = e.type.indexOf('touch') < 0 ? e.pageX : e.touches[0].pageX,
            pageY = e.type.indexOf('touch') < 0 ? e.pageY : e.touches[0].pageY;

        return new Point(pageX, pageY);
    }

    static getInstanceByAnotherMatrix(matrix, point, reverse = true) { //eslint-disable-line
        // console.log('[Point] getInstanceByAnotherMatrix: ', matrix, point);
        if (!matrix) return;

        // let np = new DOMPoint(point.x, point.y);
        // np = np.matrixTransform(matrix.inverse());

        let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        let np = svg.createSVGPoint();
        if (point) {
            np.x = point.x;
            np.y = point.y;
        }
        np = np.matrixTransform(matrix.inverse());
        // return new Point(np.x, np.y);
        return new Point(Math.round(np.x), Math.round(np.y)); // for v-canvas-painter draw graph.
    }

    static getInstanceByAnotherMap(map, point, reverse = false) { //eslint-disable-line
        let x = point.x * (1 / map.scaling),
            y = point.y * (1 / map.scaling),
            p = new Point(map.originX + x, map.originY - y);
        return p;
    }

    static getBezierPoint(i, cp) {
        let cx = 3 * (cp[1].x - cp[0].x),
            bx = 3 * (cp[2].x - cp[1].x) - cx,
            ax = cp[3].x - cp[0].x - cx - bx,
            cy = 3 * (cp[1].y - cp[0].y),
            by = 3 * (cp[2].y - cp[1].y) - cy,
            ay = cp[3].y - cp[0].y - cy - by;
        let x = ax * Math.pow(i, 3) + bx * Math.pow(i, 2) + cx * i + cp[0].x,
            y = ay * Math.pow(i, 3) + by * Math.pow(i, 2) + cy * i + cp[0].y;
        return new Point(x, y);
    }

    static getClothoidPoints(list, step = 100) { //eslint-disable-line
        let points = [];
        let cps = list.concat([]);
        if (!cps || cps.length < 2) return points;
        if (cps.length === 2) {
            cps.push(cps[1]);
            cps.unshift(cps[0]);
        } else if (cps.length === 3) {
            cps.splice(2, 0, cps[1]);
        }
        for (let i = 0; i < cps.length - 3; i += 3) {
            let p0 = new Point(cps[i].x, cps[i].y),
                p1 = new Point(cps[i + 1].x, cps[i + 1].y),
                p2 = new Point(cps[i + 2].x, cps[i + 2].y),
                p3 = new Point(cps[i + 3].x, cps[i + 3].y);

            let estimatedLength = 0;
            estimatedLength += p0.distance(p1);
            estimatedLength += p1.distance(p2);
            estimatedLength += p2.distance(p3);

            let steps = Math.min(0.5, 1 / (estimatedLength / step)); // each 100 mm or smaller if necessary
            for (let p = steps; p < 1; p += steps) {
                let cx = 3 * (p1.x - p0.x),
                    bx = 3 * (p2.x - p1.x) - cx,
                    ax = p3.x - p0.x - cx - bx,
                    cy = 3 * (p1.y - p0.y),
                    by = 3 * (p2.y - p1.y) - cy,
                    ay = p3.y - p0.y - cy - by,
                    x = ax * Math.pow(p, 3) + bx * Math.pow(p, 2) + cx * p + p0.x,
                    y = ay * Math.pow(p, 3) + by * Math.pow(p, 2) + cy * p + p0.y;
                points.push(new Point(x, y));
            }
        }
        points.unshift(new Point(cps[0].x, cps[0].y));
        points.push(new Point(cps[cps.length - 1].x, cps[cps.length - 1].y));
        return points;
    }

    static getPointerPagePos(e) {
        let pageX = e.type.indexOf('touch') < 0 ? e.pageX : e.touches[0].pageX,
            pageY = e.type.indexOf('touch') < 0 ? e.pageY : e.touches[0].pageY;
        return new Point(pageX, pageY);
    }

    static getIsometricPos(x, y, ang = 30, flipY = false) {
        if (flipY) y = -y;
        let angle = Point.getRadians(ang);
        let gx = Math.floor((x - y) * Math.cos(angle)),
            gy = Math.floor((x + y) * Math.sin(angle));
        // let gx = x * Math.cos(angle) - y * Math.sin(angle),
        //     gy = y * Math.cos(angle) + x * Math.sin(angle);
        // let gx = x + (x * Math.cos(angle) - y * Math.sin(angle)),
        //     gy = y + (y * Math.cos(angle) + x * Math.sin(angle));
        if (flipY) gy = -gy;
        return new Point(gx, gy);

        // return new Point(x, y).rotate(new Point(x, y), 30);
    }
    static getPosFromIsometric(x, y) {
        let angle = Point.getRadians(30);
        let wx = Math.floor((x * Math.sin(angle) + y * Math.cos(angle)) / (2 * Math.sin(angle) * Math.cos(angle))),
            wy = Math.floor((y * Math.cos(angle) - x * Math.sin(angle)) / (2 * Math.sin(angle) * Math.cos(angle)));
        return new Point(wx, wy);
    }

    static getProgressPoint() {
        // let h = 0,
        //     lx = 1,
        //     ly = 1;
        // if (point.h < 90) {
        //     h = Math.round(point.h);
        //     ly = -1;
        // } else if (point.h < 180) {
        //     h = Math.round(180 - point.h);
        //     lx = -1;
        //     ly = -1;
        // } else if (point.h < 270) {
        //     h = Math.round(point.h - 180);
        //     lx = -1;
        // } else {
        //     h = Math.round(360 - point.h);
        // }
        // let px = Math.cos(h) * -offset.y * lx,
        //     py = Math.sin(h) * offset.x * ly;
        // console.log(point.h, point.a);
        // return new Point(px, py);
        return new Point(0, 0);
    }

    // static getPathStockArea(points, stockWidth) {
    //     // console.log('[Point] getPathStockArea: ', points, stockWidth);
    // }

    static getDimensions(points = []) {
        let rtn = null;
        if (!points || !points.length) return rtn;
        // if (points.length === 2)
        //     return [
        //         Math.min(points[0].x, points[1].x),
        //         Math.max(points[0].x, points[1].x),
        //         Math.min(points[0].y, points[1].y),
        //         Math.max(points[0].y, points[1].y)
        //     ];
        let minx = points[0].x,
            maxx = points[0].x,
            miny = points[0].y,
            maxy = points[0].y;
        points.forEach(p => {
            if (p.x < minx) minx = p.x;
            if (p.x > maxx) maxx = p.x;
            if (p.y < miny) miny = p.y;
            if (p.y > maxy) maxy = p.y;
        });
        return [minx, maxx, miny, maxy];
    }

    static checkDimensionsIntersect([x1, x2, y1, y2], [minx, maxx, miny, maxy]) {
        if (
            typeof x1 === 'undefined' ||
            typeof x2 === 'undefined' ||
            typeof y1 === 'undefined' ||
            typeof y2 === 'undefined'
        )
            return true;
        return !(x1 >= maxx || x2 <= minx || y1 >= maxy || y2 <= miny);
    }

    static checkPointInRect({ x, y }, [minx, maxx, miny, maxy]) {
        return x >= minx && x <= maxx && y >= miny && y <= maxy;
    }

    static checkPointInCircle(ox, oy, r, x, y) {
        return (x - ox) * (x - ox) + (y - oy) * (y - oy) < r * r;
    }

    static checkPointInPolyLine(p, points) {
        //射线法
        let leftSide = 0;
        const A = p;
        for (let i = 0; i < points.length; i++) {
            let B, C;
            if (i === points.length - 1) {
                B = {
                    x: points[i].x,
                    y: points[i].y
                };
                C = {
                    x: points[0].x,
                    y: points[0].y
                };
            } else {
                B = {
                    x: points[i].x,
                    y: points[i].y
                };
                C = {
                    x: points[i + 1].x,
                    y: points[i + 1].y
                };
            }
            //判断左侧相交
            let sortByY = [B.y, C.y].sort((a, b) => a - b);
            if (sortByY[0] < A.y && sortByY[1] > A.y) {
                if (B.x < A.x || C.x < A.x) {
                    leftSide++;
                }
            }
        }
        return leftSide % 2 === 1;
    }
}

export default Point;
